use std::error::Error;

use crate::identity_card::region_code_map::REGION_CODE_MAP;

mod region_code_map;

struct IdInfo {
    region: String,
    birth_year: u16,
    birth_month: u8,
    birth_day: u8,
    // 0: female, 1: male
    gender: u8,
}

struct InvalidId;

impl<E: Error> From<E> for InvalidId {
    fn from(_: E) -> Self {
        InvalidId
    }
}

fn check_valid_date(year: u16, month: u8, day: u8) -> Result<(), InvalidId> {
    const DAYS_OF_MONTH: [u8; 13] = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    ((1..=12).contains(&month) && (1..=DAYS_OF_MONTH[month as usize]).contains(&day)
        || (month == 2
            && day == 29
            && (
                // Is leap year
                year % 4 == 0 && year % 100 != 0 || year % 400 == 0
            )))
    .then_some(())
    .ok_or(InvalidId)
}

fn id_card_15(id_card_no: &str) -> Result<IdInfo, InvalidId> {
    let region = REGION_CODE_MAP
        .get(&id_card_no[..6])
        .ok_or(InvalidId)?
        .to_string();
    let birth_year = 1900 + id_card_no[6..8].parse::<u16>()?;
    let birth_month = id_card_no[8..10].parse()?;
    let birth_day = id_card_no[10..12].parse()?;
    check_valid_date(birth_year, birth_month, birth_day)?;
    Ok(IdInfo {
        region,
        birth_year,
        birth_month,
        birth_day,
        gender: id_card_no.as_bytes()[14] % 2,
    })
}

fn id_card_18(id_card_no: &str) -> Result<IdInfo, InvalidId> {
    // checksum

    let bytes = id_card_no.as_bytes();
    if bytes[..17].iter().any(|&a| !a.is_ascii_digit()) {
        Err(InvalidId)?
    }
    const WEIGHTS: [u8; 17] = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];

    let sum = bytes
        .iter()
        .take(17)
        .zip(WEIGHTS)
        .map(|(a, b)| ((a - b'0') * b) % 11)
        // will never overflow since 10 * 17 = 170 < 256
        .sum::<u8>()
        % 11;

    match (sum % 11, bytes[17]) {
        (2, b'X') => {}
        (a, b) if b.is_ascii_digit() && (a + b - b'0') % 11 == 1 => {}
        _ => Err(InvalidId)?,
    }

    let region = REGION_CODE_MAP
        .get(&id_card_no[..6])
        .ok_or(InvalidId)?
        .to_string();
    let birth_year = id_card_no[6..10].parse()?;
    let birth_month = id_card_no[10..12].parse()?;
    let birth_day = id_card_no[12..14].parse()?;

    check_valid_date(birth_year, birth_month, birth_day)?;

    Ok(IdInfo {
        region,
        birth_year,
        birth_month,
        birth_day,
        // A + b'0' === A (mod 2)
        gender: bytes[16] % 2,
    })
}

fn check_id_card_internal(id_card_no: &str) -> Result<IdInfo, InvalidId> {
    Ok(match id_card_no.len() {
        15 => id_card_15(id_card_no)?,
        18 => id_card_18(id_card_no)?,
        _ => Err(InvalidId)?,
    })
}

pub fn check_id_card(id_card_no: &str) -> String {
    match check_id_card_internal(id_card_no) {
        Ok(info) => {
            let gender = if info.gender == 0 { "女" } else { "男" };
            format!(
                "身份证号码正确,{},{:04}年{:02}月{:02}日,{}",
                gender, info.birth_year, info.birth_month, info.birth_day, info.region
            )
        }
        Err(_) => "身份证号码错误".to_string(),
    }
}
